package edu.northwestern.cbits.purple_robot_manager.probes.features.p20;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.util.Log;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
import edu.northwestern.cbits.purple_robot_manager.probes.features.p20.Clip.ClipException;
import edu.northwestern.cbits.purple_robot_manager.probes.features.p20.FeatureExtractor.Feature;
public class P20FeaturesProbe extends Probe implements SensorEventListener
{
public static final String NAME = "edu.northwestern.cbits.purple_robot_manager.probes.features.P20FeaturesProbe";
private static final String ENABLE = "config_probe_p20_enabled";
private static final boolean DEFAULT_ENABLED = false;
private static final long WINDOW_SIZE = (long) 4e9; // sensor timestamps are
// in nanoseconds
private static final long WINDOW_SHIFT = (long) 3e9; // sensor timestamps
// are in nanoseconds
private FeatureExtractor _accelerometerExtractor = null;
private FeatureExtractor _gyroscopeExtractor = null;
private FeatureExtractor _barometerExtractor = null;
private final HashMap<Feature, Double> _featureValues = new HashMap<>();
private Clip _accelerometerClip = null;
private Clip _gyroscopeClip = null;
private Clip _barometerClip = null;
private int _lastFrequency = -1;
private Context _context = null;
private boolean _extractFeatures = false;
private final FeatureExtractor.Feature[] _featureList =
{ Feature.ACC_MEAN, Feature.ACCX_MEAN, Feature.ACCY_MEAN, Feature.ACCZ_MEAN, Feature.ACC_MEAN_ABS, Feature.ACCX_MEAN_ABS, Feature.ACCY_MEAN_ABS, Feature.ACCZ_MEAN_ABS, Feature.ACCX_STD, Feature.ACCY_STD, Feature.ACCZ_STD, Feature.ACCX_SKEW, Feature.ACCY_SKEW, Feature.ACCZ_SKEW, Feature.ACCX_KURT, Feature.ACCY_KURT, Feature.ACCZ_KURT, Feature.ACCX_DIFF_MEAN, Feature.ACCY_DIFF_MEAN, Feature.ACCZ_DIFF_MEAN, Feature.ACCX_DIFF_STD, Feature.ACCY_DIFF_STD, Feature.ACCZ_DIFF_STD, Feature.ACCX_DIFF_SKEW, Feature.ACCY_DIFF_SKEW, Feature.ACCZ_DIFF_SKEW, Feature.ACCX_DIFF_KURT, Feature.ACCY_DIFF_KURT, Feature.ACCZ_DIFF_KURT,
Feature.ACCX_MAX, Feature.ACCY_MAX, Feature.ACCZ_MAX, Feature.ACCX_MIN, Feature.ACCY_MIN, Feature.ACCZ_MIN, Feature.ACCX_MAX_ABS, Feature.ACCY_MAX_ABS, Feature.ACCZ_MAX_ABS, Feature.ACCX_MIN_ABS, Feature.ACCY_MIN_ABS, Feature.ACCZ_MIN_ABS, Feature.ACCX_RMS, Feature.ACCY_RMS, Feature.ACCZ_RMS, Feature.ACC_CROSS_XY, Feature.ACC_CROSS_YZ, Feature.ACC_CROSS_ZX, Feature.ACC_CROSS_XY_ABS, Feature.ACC_CROSS_YZ_ABS, Feature.ACC_CROSS_ZX_ABS, Feature.ACC_CROSS_XY_NORM, Feature.ACC_CROSS_YZ_NORM, Feature.ACC_CROSS_ZX_NORM, Feature.ACC_CROSS_XY_NORM_ABS, Feature.ACC_CROSS_YZ_NORM_ABS, Feature.ACC_CROSS_ZX_NORM_ABS,
Feature.ACCX_FFT1, Feature.ACCX_FFT2, Feature.ACCX_FFT3, Feature.ACCX_FFT4, Feature.ACCX_FFT5, Feature.ACCX_FFT6, Feature.ACCX_FFT7, Feature.ACCX_FFT8, Feature.ACCX_FFT9, Feature.ACCX_FFT10, Feature.ACCY_FFT1, Feature.ACCY_FFT2, Feature.ACCY_FFT3, Feature.ACCY_FFT4, Feature.ACCY_FFT5, Feature.ACCY_FFT6, Feature.ACCY_FFT7, Feature.ACCY_FFT8, Feature.ACCY_FFT9, Feature.ACCY_FFT10, Feature.ACCZ_FFT1, Feature.ACCZ_FFT2, Feature.ACCZ_FFT3, Feature.ACCZ_FFT4, Feature.ACCZ_FFT5, Feature.ACCZ_FFT6, Feature.ACCZ_FFT7, Feature.ACCZ_FFT8, Feature.ACCZ_FFT9, Feature.ACCZ_FFT10, Feature.ACCX_HIST1, Feature.ACCX_HIST2,
Feature.ACCX_HIST3, Feature.ACCX_HIST4, Feature.ACCX_HIST5, Feature.ACCX_HIST6, Feature.ACCY_HIST1, Feature.ACCY_HIST2, Feature.ACCY_HIST3, Feature.ACCY_HIST4, Feature.ACCY_HIST5, Feature.ACCY_HIST6, Feature.ACCZ_HIST1, Feature.ACCZ_HIST2, Feature.ACCZ_HIST3, Feature.ACCZ_HIST4, Feature.ACCZ_HIST5, Feature.ACCZ_HIST6, Feature.GYR_MEAN, Feature.GYRX_MEAN, Feature.GYRY_MEAN, Feature.GYRZ_MEAN, Feature.GYR_MEAN_ABS, Feature.GYRX_MEAN_ABS, Feature.GYRY_MEAN_ABS, Feature.GYRZ_MEAN_ABS, Feature.GYRX_STD, Feature.GYRY_STD, Feature.GYRZ_STD, Feature.GYRX_SKEW, Feature.GYRY_SKEW, Feature.GYRZ_SKEW, Feature.GYRX_KURT,
Feature.GYRY_KURT, Feature.GYRZ_KURT, Feature.GYRX_DIFF_MEAN, Feature.GYRY_DIFF_MEAN, Feature.GYRZ_DIFF_MEAN, Feature.GYRX_DIFF_STD, Feature.GYRY_DIFF_STD, Feature.GYRZ_DIFF_STD, Feature.GYRX_DIFF_SKEW, Feature.GYRY_DIFF_SKEW, Feature.GYRZ_DIFF_SKEW, Feature.GYRX_DIFF_KURT, Feature.GYRY_DIFF_KURT, Feature.GYRZ_DIFF_KURT, Feature.GYRX_MAX, Feature.GYRY_MAX, Feature.GYRZ_MAX, Feature.GYRX_MIN, Feature.GYRY_MIN, Feature.GYRZ_MIN, Feature.GYRX_MAX_ABS, Feature.GYRY_MAX_ABS, Feature.GYRZ_MAX_ABS, Feature.GYRX_MIN_ABS, Feature.GYRY_MIN_ABS, Feature.GYRZ_MIN_ABS, Feature.GYRX_RMS, Feature.GYRY_RMS, Feature.GYRZ_RMS,
Feature.GYR_CROSS_XY, Feature.GYR_CROSS_YZ, Feature.GYR_CROSS_ZX, Feature.GYR_CROSS_XY_ABS, Feature.GYR_CROSS_YZ_ABS, Feature.GYR_CROSS_ZX_ABS, Feature.GYR_CROSS_XY_NORM, Feature.GYR_CROSS_YZ_NORM, Feature.GYR_CROSS_ZX_NORM, Feature.GYR_CROSS_XY_NORM_ABS, Feature.GYR_CROSS_YZ_NORM_ABS, Feature.GYR_CROSS_ZX_NORM_ABS, Feature.GYRX_FFT1, Feature.GYRX_FFT2, Feature.GYRX_FFT3, Feature.GYRX_FFT4, Feature.GYRX_FFT5, Feature.GYRX_FFT6, Feature.GYRX_FFT7, Feature.GYRX_FFT8, Feature.GYRX_FFT9, Feature.GYRX_FFT10, Feature.GYRY_FFT1, Feature.GYRY_FFT2, Feature.GYRY_FFT3, Feature.GYRY_FFT4, Feature.GYRY_FFT5, Feature.GYRY_FFT6,
Feature.GYRY_FFT7, Feature.GYRY_FFT8, Feature.GYRY_FFT9, Feature.GYRY_FFT10, Feature.GYRZ_FFT1, Feature.GYRZ_FFT2, Feature.GYRZ_FFT3, Feature.GYRZ_FFT4, Feature.GYRZ_FFT5, Feature.GYRZ_FFT6, Feature.GYRZ_FFT7, Feature.GYRZ_FFT8, Feature.GYRZ_FFT9, Feature.GYRZ_FFT10, Feature.GYRX_HIST1, Feature.GYRX_HIST2, Feature.GYRX_HIST3, Feature.GYRX_HIST4, Feature.GYRX_HIST5, Feature.GYRX_HIST6, Feature.GYRY_HIST1, Feature.GYRY_HIST2, Feature.GYRY_HIST3, Feature.GYRY_HIST4, Feature.GYRY_HIST5, Feature.GYRY_HIST6, Feature.GYRZ_HIST1, Feature.GYRZ_HIST2, Feature.GYRZ_HIST3, Feature.GYRZ_HIST4, Feature.GYRZ_HIST5, Feature.GYRZ_HIST6 };
private boolean _hasAccelerometer = false;
private boolean _hasGyroscope = false;
private boolean _hasBarometer = false;
private long last_timestamp;
boolean acc_writing = false;
boolean gyr_writing = false;
boolean bar_writing = false;
private Thread _featureThread = null;
private Runnable _featureRunnable = null;
@Override
public String getPreferenceKey() {
return "features_p20";
}
public P20FeaturesProbe()
{
this._accelerometerClip = new Clip(3, P20FeaturesProbe.WINDOW_SIZE, Clip.ACCELEROMETER);
this._gyroscopeClip = new Clip(3, P20FeaturesProbe.WINDOW_SIZE, Clip.GYROSCOPE);
this._barometerClip = new Clip(1, P20FeaturesProbe.WINDOW_SIZE, Clip.BAROMETER);
// lists to be passed to FeatureExtractor
ArrayList<Feature> accelerometerFeatures = new ArrayList<>();
ArrayList<Feature> gyroscopeFeatures = new ArrayList<>();
ArrayList<Feature> barometerFeatures = new ArrayList<>();
for (Feature f : this._featureList)
{
String featureName = f.toString().toLowerCase();
if (featureName.startsWith("acc"))
{
this._hasAccelerometer = true;
accelerometerFeatures.add(f);
}
else if (featureName.startsWith("gyr"))
{
this._hasGyroscope = true;
gyroscopeFeatures.add(f);
}
else if (featureName.startsWith("bar"))
{
this._hasBarometer = true;
barometerFeatures.add(f);
}
}
// initializing feature extractors
this._accelerometerExtractor = new FeatureExtractor(P20FeaturesProbe.WINDOW_SIZE, accelerometerFeatures, 3);
this._gyroscopeExtractor = new FeatureExtractor(P20FeaturesProbe.WINDOW_SIZE, gyroscopeFeatures, 3);
this._barometerExtractor = new FeatureExtractor(P20FeaturesProbe.WINDOW_SIZE, barometerFeatures, 1);
final P20FeaturesProbe me = this;
this._featureRunnable = new Runnable()
{
@Override
public void run()
{
while (me._extractFeatures)
{
long now = System.currentTimeMillis();
boolean generateTone = false;
// checking if the clip has moved since last time
if (me._accelerometerClip.getTimestamps().size() > 0)
{
if (me._accelerometerClip.getLastTimestamp() == last_timestamp)
{
Log.e("PR", "P20FeaturesProbe: Clip hasn't moved since last feature extraction!");
generateTone = true;
}
else
{
last_timestamp = me._accelerometerClip.getLastTimestamp();
Log.e("PR", "P20FeaturesProbe: n_samp = " + me._accelerometerClip.getTimestamps().size());
}
}
if (me._hasAccelerometer)
{
synchronized (me._accelerometerClip)
{
me._featureValues.putAll(me._accelerometerExtractor.extractFeatures(me._accelerometerClip));
if (me._accelerometerClip.getValues().size() < 100)
generateTone = true;
}
}
if (me._hasGyroscope)
{
synchronized (me._gyroscopeClip)
{
me._featureValues.putAll(me._gyroscopeExtractor.extractFeatures(me._gyroscopeClip));
if (me._gyroscopeClip.getValues().size() < 100)
generateTone = true;
}
}
if (me._hasBarometer)
{
synchronized (me._barometerClip)
{
me._featureValues.putAll(me._barometerExtractor.extractFeatures(me._barometerClip));
if (me._barometerClip.getValues().size() < 100)
generateTone = true;
}
}
// transmit feature values and time stamps
me.transmitAnalysis();
try
{
if (generateTone)
{
// creating a tone generator
// the second argument is the volume (0-100)
ToneGenerator toneGenerator = new ToneGenerator(AudioManager.STREAM_ALARM, 50);
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 200);
}
// measuring the processing time
long deltaT = System.currentTimeMillis() - now;
me._featureValues.put(Feature.PROCESSING_TIME, (double) deltaT);
// accounting for the processing time / also converting
// from ns to ms
long sleepTime = P20FeaturesProbe.WINDOW_SHIFT / (long) 1e6 - deltaT;
// in the rare case that processing time is greater than
// window shift interval
if (sleepTime < 0)
sleepTime = 0;
Thread.sleep(sleepTime);
}
catch (Exception e)
{
me._extractFeatures = false;
e.printStackTrace();
}
}
}
};
}
@Override
public String name(Context context)
{
return P20FeaturesProbe.NAME;
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_probe_p20_features);
}
@Override
public String probeCategory(Context context)
{
return context.getString(R.string.probe_misc_category);
}
@Override
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(this.summary(context));
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(P20FeaturesProbe.ENABLE);
enabled.setDefaultValue(P20FeaturesProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
return screen;
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_probe_p20_features);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
Editor e = prefs.edit();
e.putBoolean(P20FeaturesProbe.ENABLE, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
Editor e = prefs.edit();
e.putBoolean(P20FeaturesProbe.ENABLE, false);
e.commit();
}
@Override
public boolean isEnabled(final Context context)
{
if (this._context == null)
this._context = context.getApplicationContext();
boolean enabled = super.isEnabled(context);
SharedPreferences prefs = Probe.getPreferences(context);
if (enabled)
enabled = prefs.getBoolean(P20FeaturesProbe.ENABLE, P20FeaturesProbe.DEFAULT_ENABLED);
SensorManager sensors = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
Sensor accelerometer = sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Sensor gyroscope = sensors.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
if (enabled)
{
int frequency = SensorManager.SENSOR_DELAY_GAME; // TODO: Set in
// preferences!
if (this._lastFrequency != frequency)
{
sensors.unregisterListener(this, accelerometer);
sensors.unregisterListener(this, gyroscope);
switch (frequency)
{
case SensorManager.SENSOR_DELAY_FASTEST:
sensors.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST, null);
sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_FASTEST, null);
break;
case SensorManager.SENSOR_DELAY_GAME:
sensors.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME, null);
sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_GAME, null);
break;
case SensorManager.SENSOR_DELAY_UI:
sensors.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI, null);
sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_UI, null);
break;
case SensorManager.SENSOR_DELAY_NORMAL:
sensors.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL, null);
sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_NORMAL, null);
break;
default:
sensors.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME, null);
sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_GAME, null);
break;
}
this._lastFrequency = frequency;
}
this._extractFeatures = true;
// checking if the current thread is still running
if (this._featureThread == null || this._featureThread.isAlive() == false)
{
// A dead thread cannot be restarted. A new thread has to be
// created.
this._featureThread = new Thread(this._featureRunnable);
this._featureThread.start();
}
return true;
}
else
{
sensors.unregisterListener(this, accelerometer);
sensors.unregisterListener(this, gyroscope);
this._lastFrequency = -1;
this._extractFeatures = false;
}
return false;
}
@Override
public void onAccuracyChanged(Sensor arg0, int arg1)
{
// TODO Auto-generated method stub
}
@Override
public void onSensorChanged(SensorEvent event)
{
// Sohrob - configure this!
Sensor sensor = event.sensor;
final double[] values = new double[event.values.length];
for (int i = 0; i < event.values.length; i++)
values[i] = (double) event.values[i]; // values.length = 3: X, Y, Z
final long timestamp = event.timestamp;
try
{
switch (sensor.getType())
{
case Sensor.TYPE_ACCELEROMETER:
if (this._hasAccelerometer)
{
synchronized (this._accelerometerClip)
{
this._accelerometerClip.appendValues(values, timestamp);
}
}
break;
case Sensor.TYPE_GYROSCOPE:
if (this._hasGyroscope)
{
synchronized (this._gyroscopeClip)
{
this._gyroscopeClip.appendValues(values, timestamp);
}
}
break;
case Sensor.TYPE_PRESSURE:
if (this._hasBarometer)
{
synchronized (this._barometerClip)
{
this._barometerClip.appendValues(values, timestamp);
}
}
break;
}
}
catch (ClipException e)
{
LogManager.getInstance(this._context).logException(e);
}
}
public void transmitAnalysis()
{
Bundle bundle = new Bundle();
bundle.putString("PROBE", this.name(this._context)); // Required
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000); // Required
for (Feature f : this._featureValues.keySet())
{
bundle.putDouble(f.toString(), this._featureValues.get(f));
}
// bundle.putInt("CNT", counter);
// bundle.putLong("T", deltat);
// bundle.putLong("Trans", t_trans);
// bundle.putLong("Time", (System.currentTimeMillis() -
// (long)1.410811849e12));
this.transmitData(this._context, bundle);
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
return String.format(context.getResources().getString(R.string.summary_p20_features_probe), (int) bundle.getDouble("PROCESSING_TIME"));
}
}